﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Collections;
using SpEasyClasses;

namespace SpEasyServer
{
    public class SpEasyServer
    {
        private TcpListener m_TcpServer;
        private Settings m_Settings;
        private List<TcpClient> m_Clients;
        private float m_BytesSent = 0;
        private float m_BytesReceived = 0;
        private Boolean Debug = false;
        private IPEndPoint m_EndPoint;
        private Hashtable m_Users;

        public SpEasyServer()
        {
            Console.WriteLine("SpEasyServer version 0.1");
            Console.WriteLine("Reading settings ....");
            try
            {
                m_Settings = new Settings("./Settings.xml");
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine(ex.Message);
                Console.ReadLine();
                return;
            }

            m_Users = new Hashtable();
            m_Clients = new List<TcpClient>();
            m_EndPoint = new IPEndPoint(IPAddress.Any, m_Settings.GetInt32("Port"));

            //
            // setup server
            m_TcpServer = new TcpListener(m_EndPoint);
        }

        public void Application_Load()
        {
            m_TcpServer.Start();
            m_TcpServer.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);

            Console.WriteLine("Listening for connections on port " + m_Settings["Port"]);

            String cmd = "";
            Console.WriteLine("");
            Console.Write("> ");
            while (true)
            {
                cmd = Console.ReadLine();

                if (cmd == "quit" || cmd == "q" || cmd == "exit")
                {
                    m_Clients.ForEach(delegate(TcpClient c)
                    {
                        c.Client.Close();
                        c.Close();
                    });

                    break;
                }
                else if (cmd == "clients")
                {
                    Console.WriteLine("Connected clients:");
                    m_Clients.ForEach(delegate(TcpClient c)
                    {
                        Console.WriteLine((c.Client.RemoteEndPoint as IPEndPoint).Address.ToString());
                    });
                }
                else if (cmd == "users")
                {
                    Console.WriteLine("Connected users:");
                    m_Clients.ForEach(delegate(TcpClient c)
                    {
                        Console.WriteLine("User: " + (m_Users[c] as User).Name);
                    });
                }
                else if (cmd == "bytes")
                {
                    Console.WriteLine("Bytes sent: {0}", m_BytesSent.ToString());
                    Console.WriteLine("Bytes received: {0}", m_BytesReceived.ToString());
                }
                else if (cmd == "debug")
                {
                    Debug = true;
                    Console.WriteLine("Debug, press return to stop");
                    Console.ReadLine();
                    Debug = false;
                }

                Console.WriteLine("");
                Console.Write("> ");
            }
        }

        public void AcceptClient(IAsyncResult result)
        {
            TcpClient c = m_TcpServer.EndAcceptTcpClient(result);

            m_Users.Add(c, new User("Unkown user"));
            new Thread(new ParameterizedThreadStart(ListenTcp)).Start(c);

            Console.WriteLine("Client " + (c.Client.RemoteEndPoint as IPEndPoint).Address.ToString() + " accepted");
            m_Clients.Add(c);

            m_TcpServer.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
        }

        private void ListenTcp(Object obj)
        {
            Byte[] msgBytes = Encoding.UTF8.GetBytes("msg");

            TcpClient client = (TcpClient)obj;
            try
            {
                while (true)
                {
                    if (Debug)
                        Console.WriteLine("Listening for data from " + (client.Client.RemoteEndPoint as IPEndPoint).Address.ToString());

                    Byte[] buffer = new Byte[client.ReceiveBufferSize];
                    client.Client.Receive(buffer);

                    //
                    // check if is msg
                    if (
                        buffer[0] == msgBytes[0]
                        &&
                        buffer[1] == msgBytes[1]
                        &&
                        buffer[2] == msgBytes[2])
                    {
                        HandleMessage(client, buffer);
                    }
                    else
                    {
                        Forward fwd = new Forward()
                        {
                            Buffer = buffer,
                            Client = client
                        };
                        new Thread(new ParameterizedThreadStart(ForwardTransfer)).Start(fwd);
                    }
                }
            }
            catch (Exception ex)
            {
                if (Debug)
                    Console.WriteLine((client.Client.RemoteEndPoint as IPEndPoint).Address.ToString() +
                        " exited with error " + ex.Message);

                if (client != null && client.Client != null && client.Connected)
                {
                    if (Debug)
                        Console.WriteLine("Retrying...");

                    ListenTcp(client);
                }
                else
                {
                    m_Clients.Remove(client);
                    m_Users.Remove(client);
                }
            }
        }

        private void HandleMessage(TcpClient client, Byte[] buff)
        {
            String[] message = Encoding.UTF8.GetString(buff).Replace("\x00", "").Split("\r\n".ToCharArray());

            foreach (String msg in message)
            {
                if (msg.Replace("\r\n", "").Trim() != "")
                {
                    HandleMessage(client, msg.Replace("\r\n", "").Remove(0, 3));
                }
            }
        }

        private void HandleMessage(TcpClient client, String message)
        {
            if (Debug)
                Console.WriteLine(message);

            if (message.StartsWith("SetUsername"))
            {
                String u = message.Remove(0, "SetUsername".Length);
                (m_Users[client] as User).Name = u;

                String users = "GetUsers";
                m_Clients.ForEach(delegate(TcpClient c)
                {
                    users += (m_Users[c] as User).Name + ",";
                });

                m_Clients.ForEach(delegate(TcpClient c)
                {
                    c.SendMessage(users);
                });
            }
            else if (message.StartsWith("GetUsers"))
            {
                message = "GetUsers";
                m_Clients.ForEach(delegate(TcpClient c)
                {
                    message += (m_Users[c] as User).Name + ",";
                });
            }

            if (Debug)
                Console.WriteLine(message);

            client.SendMessage(message);
        }

        private struct Forward
        {
            public TcpClient Client;
            public Byte[] Buffer;
        }

        private void ForwardTransfer(Object obj)
        {
            Forward fwd = (Forward)obj;

            m_Clients.ForEach(delegate(TcpClient c)
            {
                if (c != null && c.Client != null && c.Connected && fwd.Client != c)
                {
                    if (Debug)
                        Console.WriteLine("Sending " + fwd.Buffer.Length + " bytes to " + (c.Client.RemoteEndPoint as IPEndPoint).Address.ToString());

                    c.Client.Send(fwd.Buffer);
                }
            });
        }
    }
}
